package com.fenquen.dfs.router.controller;

import com.alibaba.fastjson.JSON;
import com.fenquen.dfs.router.config.DFSRouterConfig;
import com.fenquen.dfs.router.exception.OriginFileNameTooLongException;
import com.fenquen.dfs.router.exception.RedirectException;
import com.fenquen.dfs.base.model.AjaxResponse;
import com.fenquen.dfs.base.protocol.data.storage_source.req_from_storage.StorageNode;
import com.fenquen.dfs.base.protocol.data.storage_source.req_from_storage.Report;
import com.fenquen.dfs.router.rocksdb.RocksDBOperator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponents;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

@Controller
public class Portal {
    private static final Logger LOGGER = LoggerFactory.getLogger(Portal.class);

    @Autowired
    private RocksDBOperator rocksDBOperator;

    @Autowired
    private DFSRouterConfig dfsRouterConfig;

    /**
     * 接收客户端的post文件上传请求
     * 通过nginx内部重定向到目标存储节点
     * 要么是获取不到可用的存储节点直接返回错误信息的json
     *
     * @param response
     */
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public void upload(@RequestParam("file") MultipartFile uploadFile,
                       Integer retriedCount,
                       HttpServletResponse response) {
        // 说明先前在router集群中一直没有找到任何可用的存储节点,并且超过了最大尝试的次数
        // if (retriedCount > dfsConfig.maxRouterRetryCount) {
        //      throw new NoAvailableStorageException();
        //  }

        try {
            // 上传的文件的大小
            Long uploadedFileSizeByte = uploadFile.getSize();

            // 获取可用的存储节点列表
            List<StorageNode> availableStorageNodeList = rocksDBOperator.getAvailableStorageList(uploadedFileSizeByte);
            // 可用空间的大小从大到小排序
            Collections.sort(availableStorageNodeList);
            sendRedirect(response, assembleRedirectURL2Upload(availableStorageNodeList, uploadFile));
        } catch (Exception e) {
            generateAjaxRespWhenFail(response, e);
        }


    }

    /**
     * 外部客户首先请求它,然后再nginx内部重定向到存储节点
     * 要是没有对应的uuid直接返回错误信息的json
     */
    @RequestMapping(value = "/download")
    public void download(@RequestParam String uploadFileUUID,
                         HttpServletRequest request,
                         HttpServletResponse response) {

        try {
            if (StringUtils.isEmpty(uploadFileUUID)) {
                throw new IllegalArgumentException("uploadFileUUID is actually empty");
            }

            // 优先使用cookie中保存的存储节点
            Cookie[] cookies = request.getCookies();
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if (uploadFileUUID.equals(cookie.getName())) {
                        sendRedirect(response, assembleRedirectURL2Download(cookie));
                        return;
                    }
                }
            }

            List<Report> reportList = rocksDBOperator.getReportList(uploadFileUUID);
            sendRedirect(response, assembleRedirectURL2DelOrDownload(reportList, true));
        } catch (Exception e) {
            generateAjaxRespWhenFail(response, e);
        }

    }

    @RequestMapping(value = "/delete")
    public void delete(@RequestParam String uploadFileUUID,
                       HttpServletResponse response) {

        try {
            List<Report> reportList = rocksDBOperator.getReportList(uploadFileUUID);
            sendRedirect(response, assembleRedirectURL2DelOrDownload(reportList, false));
        } catch (Exception e) {
            generateAjaxRespWhenFail(response, e);
        }

    }

    /**
     * 重定向到目标storage node
     *
     * @param httpServletResponse
     * @param redirectUrl
     * @throws RedirectException
     */
    private void sendRedirect(HttpServletResponse httpServletResponse,
                              String redirectUrl)
            throws RedirectException {
        try {
            httpServletResponse.sendRedirect(redirectUrl);
        } catch (Exception e) {
            throw new RedirectException(e);
        }
    }

    /**
     * 获取可用存储节点失败返回失败信息的
     *
     * @param response
     * @param throwable
     */
    private void generateAjaxRespWhenFail(HttpServletResponse response,
                                          Throwable throwable) {
        LOGGER.error(throwable.getMessage(), throwable);

        response.setContentType("application/json;charset=utf-8");
        PrintWriter printWriter;
        try {
            printWriter = response.getWriter();
        } catch (IOException e) {
            LOGGER.error("response.getWriter()", e);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return;
        }

        printWriter.print(JSON.toJSONString(AjaxResponse.fail(throwable)));
    }

    // 生成内部重定向到存储节点用来上传的url文本
    private String assembleRedirectURL2Upload(List<StorageNode> availableStorageNodeList,
                                              MultipartFile uploadFile)
            throws OriginFileNameTooLongException, UnsupportedEncodingException {
        // 上传的文件的原始名字
        String originFileName = uploadFile.getOriginalFilename();

        assert originFileName != null;

        // 文件名字太长了
        if (originFileName.length() > 80) {
            throw new OriginFileNameTooLongException(originFileName);
        }

        String extName = "";
        // 读取文件格式名
        if (!originFileName.contains(".")) {
            extName = "unknown";
        } else {
            extName = originFileName.substring(originFileName.lastIndexOf(".") + 1);
            if (StringUtils.isEmpty(extName)) {
                extName = "unknown";
            }
        }


        // 生成对应的唯一标识目前暂时使用uuid也同样起到是一趟任务标识的用途
        String uploadFileUUID = UUID.randomUUID()
                .toString()
                .replace("-", "");

        // 剩下空间最大的节点
        StorageNode target = availableStorageNodeList.get(0);

        StringBuilder replicaStorageNodes = new StringBuilder();
        // 遍历其他的存储节点
        for (int a = 1; a < availableStorageNodeList.size(); a++) {
            StorageNode replicaStorageNode = availableStorageNodeList.get(a);
            replicaStorageNodes.append(replicaStorageNode.getAddr()).append(",");
        }

        return "http://" + target.getAddr() + "/upload_" +
                "?uploadFileUUID=" + uploadFileUUID +
                "&replicaStorageNodes=" + replicaStorageNodes +
                "&originFileName=" + URLEncoder.encode(uploadFile.getOriginalFilename(), "utf-8") +
                "&extName=" + extName;
    }

    // cookie保存的格式是 地址,格式名字,原始名字
    private String assembleRedirectURL2Download(Cookie cookie) throws UnsupportedEncodingException {

        String[] elements = cookie.getValue().split("@");
        return "http://" + elements[0] + "/download_" +
                "?uploadFileUUID=" + cookie.getName() +
                "&replicaStorageNodes=" +
                "&originFileName=" + URLEncoder.encode(elements[2], "utf-8") +
                "&extName=" + elements[2];
    }

    // 生成内部重定向到存储节点的url用来删除下载的
    private String assembleRedirectURL2DelOrDownload(List<Report> reportList, boolean upload)
            throws UnsupportedEncodingException {
        Collections.shuffle(reportList);
        Report target = reportList.get(0);

        StringBuilder replicaStorageNodes = new StringBuilder();
        // 遍历其他的存储节点
        for (int a = 1; a < reportList.size(); a++) {
            replicaStorageNodes.append(reportList.get(a).getAddr()).append(",");
        }

        return "http://" + target.getAddr() + (upload ? "/download_" : "/delete_") +
                "?uploadFileUUID=" + target.uploadFileUUID +
                "&replicaStorageNodes=" + replicaStorageNodes +
                "&originFileName=" + URLEncoder.encode(target.originFileName, "utf-8") +
                "&extName=" + target.extName;
    }
}
